package org.gjy.m8.io;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author gjy
 * @version 1.0
 * @since 2025-11-10 15:39:29
 */
public class ZipCompressor {

    private static final int BUFFER_SIZE = 8192;
    private static final DateTimeFormatter DATE_FORMATTER =
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**
     * 压缩单个文件为 ZIP
     */
    public static void compressFile(String sourceFilePath, String zipFilePath)
            throws IOException {
        compressFile(Paths.get(sourceFilePath), Paths.get(zipFilePath), null);
    }

    /**
     * 压缩单个文件为 ZIP（带进度回调）
     */
    public static void compressFile(String sourceFilePath, String zipFilePath,
                                    ProgressCallback callback) throws IOException {
        compressFile(Paths.get(sourceFilePath), Paths.get(zipFilePath), callback);
    }

    /**
     * 压缩单个文件为 ZIP
     */
    public static void compressFile(Path sourceFile, Path zipFile,
                                    ProgressCallback callback) throws IOException {
        validateSourceFile(sourceFile);
        ensureParentDirectory(zipFile);

        long fileSize = Files.size(sourceFile);

        try (FileOutputStream fos = new FileOutputStream(zipFile.toFile());
             ZipOutputStream zos = new ZipOutputStream(fos, StandardCharsets.UTF_8)) {

            String fileName = sourceFile.getFileName().toString();
            ZipEntry zipEntry = new ZipEntry(fileName);
            zos.putNextEntry(zipEntry);

            byte[] buffer = new byte[BUFFER_SIZE];
            int bytesRead;
            long totalRead = 0;

            try (FileInputStream fis = new FileInputStream(sourceFile.toFile())) {
                while ((bytesRead = fis.read(buffer)) != -1) {
                    zos.write(buffer, 0, bytesRead);
                    totalRead += bytesRead;

                    if (callback != null && fileSize > 0) {
                        int progress = (int) (totalRead * 100 / fileSize);
                        callback.onProgress(progress, totalRead, fileSize);
                    }
                }
            }

            zos.closeEntry();
            logInfo("文件压缩完成: %s -> %s", sourceFile, zipFile);
        }
    }

    /**
     * 压缩整个文件夹为 ZIP
     */
    public static void compressDirectory(String sourceDirPath, String zipFilePath)
            throws IOException {
        compressDirectory(Paths.get(sourceDirPath), Paths.get(zipFilePath), null);
    }

    /**
     * 压缩整个文件夹为 ZIP（带进度回调）
     */
    public static void compressDirectory(String sourceDirPath, String zipFilePath,
                                         ProgressCallback callback) throws IOException {
        compressDirectory(Paths.get(sourceDirPath), Paths.get(zipFilePath), callback);
    }

    /**
     * 压缩整个文件夹为 ZIP
     */
    public static void compressDirectory(Path sourceDir, Path zipFile,
                                         ProgressCallback callback) throws IOException {
        if (!Files.exists(sourceDir) || !Files.isDirectory(sourceDir)) {
            throw new IllegalArgumentException("源目录不存在或不是有效目录: " + sourceDir);
        }

        ensureParentDirectory(zipFile);

        List<Path> files = collectAllFiles(sourceDir);
        long totalFiles = files.size();
        long processedFiles = 0;

        try (FileOutputStream fos = new FileOutputStream(zipFile.toFile());
             ZipOutputStream zos = new ZipOutputStream(fos, StandardCharsets.UTF_8)) {

            for (Path file : files) {
                String entryName = sourceDir.relativize(file).toString();
                // 统一使用正斜杠
                entryName = entryName.replace('\\', '/');

                ZipEntry zipEntry = new ZipEntry(entryName);
                zos.putNextEntry(zipEntry);

                long bytesRead = copyFileToZip(file, zos);
                zos.closeEntry();

                processedFiles++;

                if (callback != null) {
                    int progress = (int) (processedFiles * 100 / totalFiles);
                    callback.onProgress(progress, processedFiles, totalFiles);
                }

                logInfo("已压缩: %s (%.2f KB)", entryName, bytesRead / 1024.0);
            }
        }

        logInfo("目录压缩完成: %s -> %s", sourceDir, zipFile);
    }

    /**
     * 压缩多个文件到一个 ZIP
     */
    public static void compressMultipleFiles(List<String> filePaths, String zipFilePath)
            throws IOException {
        compressMultipleStringFiles(filePaths, zipFilePath, null);
    }

    /**
     * 压缩多个文件到一个 ZIP（带进度回调）
     */
    public static void compressMultipleStringFiles(List<String> filePaths, String zipFilePath,
                                                   ProgressCallback callback) throws IOException {
        List<Path> paths = new ArrayList<>();
        for (String path : filePaths) {
            paths.add(Paths.get(path));
        }
        compressMultipleFiles(paths, zipFilePath, callback);
    }

    /**
     * 压缩多个文件到一个 ZIP
     */
    public static void compressMultipleFiles(List<Path> filePaths, String zipFilePath,
                                             ProgressCallback callback) throws IOException {
        if (filePaths.isEmpty()) {
            throw new IllegalArgumentException("文件列表不能为空");
        }

        Path zipFile = Paths.get(zipFilePath);
        ensureParentDirectory(zipFile);

        long totalFiles = filePaths.size();
        long processedFiles = 0;

        try (FileOutputStream fos = new FileOutputStream(zipFile.toFile());
             ZipOutputStream zos = new ZipOutputStream(fos, StandardCharsets.UTF_8)) {

            for (Path file : filePaths) {
                if (!Files.exists(file) || Files.isDirectory(file)) {
                    logWarning(file);
                    continue;
                }

                String entryName = file.getFileName().toString();
                ZipEntry zipEntry = new ZipEntry(entryName);
                zos.putNextEntry(zipEntry);

                copyFileToZip(file, zos);
                zos.closeEntry();

                processedFiles++;

                if (callback != null) {
                    int progress = (int) (processedFiles * 100 / totalFiles);
                    callback.onProgress(progress, processedFiles, totalFiles);
                }
            }
        }

        logInfo("多文件压缩完成: %s (%d 个文件)", zipFile, totalFiles);
    }

    // ========== 辅助方法 ==========

    private static void validateSourceFile(Path sourceFile) throws IOException {
        if (!Files.exists(sourceFile)) {
            throw new IllegalArgumentException("源文件不存在: " + sourceFile);
        }
        if (Files.isDirectory(sourceFile)) {
            throw new IllegalArgumentException("源路径是目录，请使用 compressDirectory 方法: " + sourceFile);
        }
    }

    private static void ensureParentDirectory(Path file) throws IOException {
        Path parent = file.getParent();
        if (parent != null && !Files.exists(parent)) {
            Files.createDirectories(parent);
        }
    }

    private static List<Path> collectAllFiles(Path directory) throws IOException {
        List<Path> files = new ArrayList<>();
        Files.walk(directory)
                .filter(Files::isRegularFile)
                .forEach(files::add);
        return files;
    }

    private static long copyFileToZip(Path source, ZipOutputStream zos) throws IOException {
        long bytesCopied = 0;
        byte[] buffer = new byte[BUFFER_SIZE];
        int bytesRead;

        try (FileInputStream fis = new FileInputStream(source.toFile())) {
            while ((bytesRead = fis.read(buffer)) != -1) {
                zos.write(buffer, 0, bytesRead);
                bytesCopied += bytesRead;
            }
        }
        return bytesCopied;
    }

    private static void logInfo(String format, Object... args) {
        System.out.printf("[%s] INFO: %s%n",
                LocalDateTime.now().format(DATE_FORMATTER),
                String.format(format, args)
        );
    }

    private static void logWarning(Object... args) {
        System.err.printf("[%s] WARN: %s%n",
                LocalDateTime.now().format(DATE_FORMATTER),
                String.format("跳过不存在的文件或目录: %s", args)
        );
    }

}
